package com.hyz.service.impl;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hyz.dto.ActionDTO;
import com.hyz.dto.ActionResult;
import com.hyz.dto.ListDTO;
import com.hyz.dto.ListResult;
import com.hyz.entity.Comment;
import com.hyz.mapper.CommentsMapper;
import com.hyz.service.CommentsService;
import com.hyz.utils.RedisIdWorker;
import org.jetbrains.annotations.Nullable;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

import static com.hyz.utils.RedisConstants.*;

/**
 * @author HGD
 * @description 针对表【tb_comments】的数据库操作Service实现
 * @createDate 2023-01-27 16:42:46
 */
@Service
public class CommentsServiceImpl extends ServiceImpl<CommentsMapper, Comment>
        implements CommentsService {
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private Redisson redisson;

    @Override
    public ListResult getList(ListDTO listDTO) {
        //- 先去 Redis 中查询，是否有对应视频评论的缓存。
        String videoKey = VIDEO_ID_KEY + listDTO.getVideoId();
        ListResult commentList = getListResult(videoKey);
        //- 如果有则返回。有用户读取对应评论缓存的时候，TTL 重置。
        if (commentList != null) {
            return commentList;
        }
        //- 没有对应的缓存，则进入查询流程
        //- 先去 Redis 中获取视频评论的查询锁。
        RLock lock = redisson.getLock(listDTO.getToken());
        boolean isLock = lock.tryLock();
        try {
            if (isLock) {
                //- 获取成功之后，向MySQL中拉取数据部分数据，然后写入 Redis 当中，并唤醒阻塞线程。
                //- 写入Redis中的评论缓存，设置 TTL 为10秒。
                List<Comment> sqlCommentList = lambdaQuery().eq(Comment::getVideoId, listDTO.getVideoId()).list();
                String videoJson = JSONUtil.toJsonStr(sqlCommentList);
                stringRedisTemplate.opsForValue().set(videoKey, videoJson, 10, TimeUnit.SECONDS);
                return ListResult.ok(sqlCommentList);
            }
        } finally {
            //- 释放锁
            lock.unlock();
        }
        //- 如果获取失败则阻塞 / 获取失败则等待 2 秒后先查询再获锁
        for (int i = 0; i < 3; i++) {
            commentList = getListResult(videoKey);
            if (commentList != null) {
                return commentList;
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return ListResult.fail("错误");
    }

    /**
     * 从缓存中获取到评论数据
     *
     * @param videoKey 视频id
     * @return 评论列表
     */
    @Nullable
    private ListResult getListResult(String videoKey) {
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(videoKey))) {
            String videoJson = stringRedisTemplate.opsForValue().get(videoKey);
            if (videoJson != null) {
                stringRedisTemplate.opsForValue().set(videoKey, videoJson, 10, TimeUnit.SECONDS);
                List<Comment> commentList = JSONUtil.toList(JSONUtil.parseArray(videoJson), Comment.class);
                return ListResult.ok(commentList);
            }
        }
        return null;
    }

    @Override
    public ActionResult actionComment(ActionDTO actionDTO) {
        Integer actionType = actionDTO.getActionType();
        switch (actionType) {
            case 1:
                return insertComment(actionDTO);
            case 2:
                return deleteComment(actionDTO);
            default:
                return ActionResult.fail();
        }
    }

    private ActionResult insertComment(ActionDTO actionDTO) {
        // ## 写评论
        //删除和写操作都是按照用户为单位上锁
        // 先去写缓存中查询该用户是否有写操作，如果存在则拒绝
        String actionKey = ACTION_KEY + actionDTO.getToken();
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(actionKey))) {
            return ActionResult.fail("说这么多累了吧，喝口水在说吧");
        }
        //同一个账号同时多个地方评论
        //讨论：设置可重入次数，允许用户有一定的并发操作？
        //- 先去Redis中获取用户评论操作锁
        RLock lock = redisson.getLock(actionKey);
        boolean isLock = lock.tryLock();
        try {
            if (isLock) {
                //- 将对应的评论写入 Redis 的写缓存当中（视频id，评论id，评论）。
                RedisIdWorker redisIdWorker = new RedisIdWorker(stringRedisTemplate);
                Long commentId = redisIdWorker.nextId("comment");
                Comment comment = new Comment();
                // TODO:等待同伴的权限模块传入数据
                comment.setUserId(1L);
                comment.setCommentId(commentId);
                comment.setContent(actionDTO.getCommentText());
                comment.setVideoId(actionDTO.getVideoId());
                comment.setCreateTime(LocalDateTime.now());
                String commentJson = JSONUtil.toJsonStr(comment);
                stringRedisTemplate.opsForHash().put(INSERT_KEY, commentId, commentJson);
                //- 返回前端他的评论，并将操作记录，写到缓存当中，2秒内不允许再次操作
                return ActionResult.ok(comment);
            }
        } finally {
            stringRedisTemplate.opsForValue().set(actionKey, "0", 1, TimeUnit.SECONDS);
            lock.unlock();
        }
        stringRedisTemplate.opsForValue().set(actionKey, "0", 1, TimeUnit.SECONDS);
        return ActionResult.fail("插入失败");
    }

    private ActionResult deleteComment(ActionDTO actionDTO) {
        //## 删除评论
        //
        //同一个账号对同一条评论执行删除操作
        //
        //- 先去 Redis 中获取用户评论操作锁
        String actionKey = ACTION_KEY + actionDTO.getToken();
        // 如果缓存中显示已经最近1秒内已操作，则提示操作频繁
        if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(actionKey))) {
            return ActionResult.fail("说这么多累了吧，喝口水在说吧");
        }
        Long commentId = actionDTO.getCommentId();
        // 生成用户操作锁，只有拿到锁才可以去操作写评论或删除评论
        RLock userLock = redisson.getLock(actionKey);
        // 生成写缓存锁，如果获取了就可以去写缓存中进行操作。
        RLock cacheLock = redisson.getLock(INSET_CACHE_KEY);
        try {
            // 尝试获取操作锁
            if (userLock.tryLock()) {
                //- 尝试获取写缓存锁并且在 Redis 的写缓存中查询是否存在对应的缓存数据
                if (cacheLock.tryLock() && Boolean.TRUE.equals(stringRedisTemplate.opsForHash().hasKey(INSERT_KEY, commentId))) {
                    // 如果写缓存中存在数据则直接删除并返回结果，释放锁
                    stringRedisTemplate.opsForHash().delete(INSERT_KEY, commentId);
                } else {
                    //- 没有则将对应的删除操作写入 Redis 的删除缓存当中，释放锁。
                    stringRedisTemplate.opsForSet().add(DELETE_KEY, actionDTO.getCommentId().toString());
                }
                return ActionResult.ok("删除成功");
            }
        } finally {
            // 将尝试操作的用户放入缓存中3秒，1秒内不允许有再次操作
            stringRedisTemplate.opsForValue().set(actionKey, "0", 1, TimeUnit.SECONDS);
            cacheLock.unlock();
            userLock.unlock();
        }
        // 将尝试操作的用户放入缓存中3秒，1秒内不允许有再次操作
        stringRedisTemplate.opsForValue().set(actionKey, "0", 1, TimeUnit.SECONDS);
        return ActionResult.fail("删除失败");
    }
}




